package me.yushuai.wukong.base.service.impl;

import me.yushuai.wukong.base.api.dto.AllocateLocationDTO;
import me.yushuai.wukong.base.api.dto.CreateLocationDTO;
import me.yushuai.wukong.base.api.dto.LocDTO;
import me.yushuai.wukong.base.api.dto.UpdateLocationDTO;
import me.yushuai.wukong.base.convertor.LocationConvertor;
import me.yushuai.wukong.base.entity.Location;
import me.yushuai.wukong.base.entity.LocationAttribute;
import me.yushuai.wukong.base.repository.LocationRepository;
import me.yushuai.wukong.base.service.LocGroupService;
import me.yushuai.wukong.base.service.LocService;
import me.yushuai.wukong.base.service.WorkingAreaService;
import me.yushuai.wukong.base.service.ZoneService;
import me.yushuai.wukong.common.api.AbstractPageable;
import me.yushuai.wukong.common.dto.PageDTO;
import me.yushuai.wukong.common.error.WuKongException;
import me.yushuai.wukong.common.exceptin.BizIdNotExistException;
import me.yushuai.wukong.common.exceptin.BusinessException;
import me.yushuai.wukong.common.exceptin.NotExistException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * 库位业务实现类
 *
 * @author zhoushuai@189.cn
 * @since 2022-08-31
 */
@Service
public class LocationServiceImpl implements LocService {

    /**
     * 库位编号不存在错误编码
     */
    public static final String LOCATION_ID_NOT_FOUND = "location.locationId.notFound";

    /**
     * 库位编码已存在错误编码
     */
    public static final String LOCATION_CODE_HAS_EXIST = "location.bizId.HasExist";

    /**
     * 库位编号与库位编码不匹配
     */
    public static final String ID_AND_BIZ_ID_NOT_EXISTS = "location.locationIdAndLocationCode.NotMatch";

    @Autowired
    private LocationConvertor locationConvertor;

    @Autowired
    private LocationRepository locationRepository;

    @Autowired
    private LocGroupService locGroupService;

    @Autowired
    private ZoneService zoneService;

    @Autowired
    private WorkingAreaService workingAreaService;


    @Override
    public Long createLocation(CreateLocationDTO dto) throws WuKongException {
        //1. 检查SKU是存在
        this.assertBizIdNotExist(dto.getBizId());

        //2. 检查库区所属的组是否存在
        final Long locGroup1Id = dto.getLocGroup1Id();
        final Long locGroup2Id = dto.getLocGroup2Id();
        this.checkLocGroupsIsAvailable(locGroup1Id, locGroup2Id);

        //3. 检查库区是否存在
        final Long pickZoneId = dto.getPickZoneId();
        final Long putAwayZoneId = dto.getPutAwayZoneId();

        if (Objects.isNull(pickZoneId)) {
            throw new NotExistException(pickZoneId, "Zone.pickZoneId");
        }

        if (Objects.isNull(putAwayZoneId)) {
            throw new NotExistException(putAwayZoneId, "Zone.putAwayZoneId");
        }


        //4. 检查工作区是否合法
        final Long workingAreaId = dto.getWorkingAreaId();
        this.workingAreaService.assertWorkingAreaIdHasExist(workingAreaId);

        //3. 新增库位
        final Location location = this.locationConvertor.mapToLocation(dto);
        this.locationRepository.save(location);
        return location.getId();
    }


    @Override
    public Long updateLocation(UpdateLocationDTO dto) throws WuKongException {

        //1. 检查库位是否存在
        final Long locationId = dto.getId();
        final Location location = getLocationOrThrow(locationId);

        //2. 检查库位组是否合法
        this.checkLocGroupsIsAvailable(dto.getLocGroup1Id(), dto.getLocGroup2Id());

        //3. 检查库区是否存在
        final Long pickZoneId = dto.getPickZoneId();
        final Long putAwayZoneId = dto.getPutAwayZoneId();

        if (Objects.isNull(pickZoneId)) {
            throw new NotExistException(pickZoneId, "Zone.pickZoneId");
        }

        if (Objects.isNull(putAwayZoneId)) {
            throw new NotExistException(putAwayZoneId, "Zone.putAwayZoneId");
        }

        //4. 检查工作区是否合法
        final Long workingAreaId = dto.getWorkingAreaId();
        this.workingAreaService.assertWorkingAreaIdHasExist(workingAreaId);


        //5. 通过库位编号更新库位信息
        this.locationConvertor.updateToLocation(dto, location);
        this.locationRepository.save(location);

        return dto.getId();
    }

    @Override
    public void deleteLocation(Long locationId) throws WuKongException {
        //1. 检查库位是否存在
        this.assertLocationIdHasExist(locationId);

        //2. 删除库位记录
        this.locationRepository.deleteById(locationId);
    }

    @Override
    public LocDTO findById(Long locationId) throws NotExistException {

        //1. 通过locationId查询库位信息
        final Location location = getLocationOrThrow(locationId);

        //2. 返回查询结果
        return this.locationConvertor.mapToLocationDto(location);
    }

    @Override
    public LocDTO findByBizId(String bizId) throws BizIdNotExistException {
        final Location location = getLocationOrThrow(bizId);
        return this.locationConvertor.mapToLocationDto(location);
    }


    @Override
    public boolean allowedReceiving(Long locId) throws NotExistException {
        //1. checking the location if exists.
        final Location location = getLocationOrThrow(locId);

        //2. checking the locationAttribute of Location is allowed receiving.
        final LocationAttribute locAttr = location.getLocationAttribute();
        if (LocationAttribute.NOT_ALLOWED.contains(locAttr)) {
            return false;
        }

        //3. the Location with the locId is holdState is true.
        // TODO 因为Location中没有存放冻结的标识，所以必须从ACT_InventoryHold中来获取库位的冻结状态，
        // 为什么不直接在引用ACT_InventoryHold的业务接口和Repository实现业务逻辑，这是因为这样可能存在
        // 循环依赖的问题，所以需要重新设计来避免循环依赖的问题

        return true;
    }

    @Override
    public LocDTO allocateLocation(AllocateLocationDTO dto) {
        return null;
    }

    @Override
    public List<LocDTO> findAllByIds(Collection<Long> ids) throws BusinessException {
        final List<Location> locations = this.locationRepository.findAllById(ids);
        return this.locationConvertor.mapToLocationDtoList(locations);
    }

    @Override
    public PageDTO<LocDTO> pageSearchLoc(AbstractPageable pageable, Specification<Location> spec) {

        //1.准备查询条件
        Pageable page = PageRequest.of(pageable.getCurrent(), pageable.getSize());
        Specification<Location> querySpec = spec;
        if (Objects.isNull(querySpec)) {
            querySpec = (root, query, criteriaBuilder) -> query.getRestriction();
        }

        //2. 执行分页条件查询
        final Page<Location> locationPage = this.locationRepository.findAll(querySpec, page);
        pageable.setTotal(locationPage.getTotalElements());
        final List<LocDTO> list = this.locationConvertor.mapToLocationDtoList(locationPage.getContent());

        //3. 构建返回对象并返回结果
        return PageDTO.of(pageable, list);
    }

    @Override
    public void assertLocationIdHasExist(Long locationId) throws WuKongException {
        if (!this.locationRepository.existsById(locationId)) {
            throw new NotExistException(locationId, Location.class);
        }
    }

    @Override
    public void assertBizIdNotExist(String bizId) throws WuKongException {
        final Specification<Location> spec = (root, query, criteriaBuilder) -> criteriaBuilder.and(
                criteriaBuilder.equal(root.get("bizId").as(String.class), bizId)
        );
        if (this.locationRepository.count(spec) > 0) {
            throw new WuKongException(LOCATION_CODE_HAS_EXIST, bizId);
        }
    }

    @Override
    public Boolean checkHasExistLocationId(Long locationId) {
        return this.locationRepository.existsById(locationId);
    }

    @Override
    public boolean existsByWarehouseIdAndLocId(Long warehouseId, Long locId) {
        return this.locationRepository.existsByWarehouseIdAndLocId(warehouseId, locId);
    }

    @Override
    public boolean existsByWarehouseBizIdAndLocBizId(String warehouseBizId, String locBizId) {
        return false;
    }


    // ------------------------------------------private method-----------------------------------------------

    private Location getLocationOrThrow(Long locationId) throws NotExistException {
        return this.locationRepository.findById(locationId)
                //2. 检查库位信息是否存在并抛出异常
                .orElseThrow(() -> new NotExistException(locationId, Location.class));
    }

    private Location getLocationOrThrow(String bizId) throws BizIdNotExistException {
        return this.locationRepository.findByBizId(bizId)
                .orElseThrow(() -> new BizIdNotExistException(bizId, Location.class));
    }

    /**
     * 检查库位所属的库区是否有效
     *
     * @param locGroup1Id 所属库位组1
     * @param locGroup2Id 所属库位组
     * @throws WuKongException 当所属库位组不为空并且库位组编码不存在时抛出异常
     */
    private void checkLocGroupsIsAvailable(Long locGroup1Id, Long locGroup2Id) throws WuKongException {

        //1. 当库位所属的1组不为空时检查组编号是否存在
        if (Objects.nonNull(locGroup1Id)) {
            this.locGroupService.assertLocGroupIdExist(locGroup1Id);
        }

        //2. 当库位所属的2组不为空时检查组编号是否存在
        if (Objects.nonNull(locGroup2Id)) {
            this.locGroupService.assertLocGroupIdExist(locGroup2Id);
        }
    }
}




